//
//  LeGameObjectManager.cpp
//  Leste
//
//  Created by 王乐 on 2022/1/28.
//

#include "LeGameObjectManager.h"
#include "LeGameObject.h"

#include "LeCameraManager.h"
#include "LeCameraObject.h"

#include "LeInput.h"

#include "LeCubeGameObject.h"
#include "LeLightGameObject.h"
#include "LeQuadGameObject.h"
#include "LeGroundGameObject.h"
#include "LeBackGroundGameObject.h"
#include "LePlayerGameObject.h"
#include "LeCubicBoxGameObject.h"
#include "LeCubicSubBoxGameObject.h"
#include "LeFireGameObject.h"
#include "LeFireworkGameObject.h"

#include "LePostProcessingGameObject.h"

#include "LePhysicsQuadSpawnerGameObject.h"

LeGameObjectManager::LeGameObjectManager() {
    m_CurID = 0;
    m_pMapGameObjects.clear();
    m_arrGameObjIDstoDestroy.clear();
}

LeGameObjectManager::~LeGameObjectManager() { 

}

// Initialize All Starting GameObjects HERE !
void LeGameObjectManager::initializeGameObjects()
{
    // Initialize mainCamera
    // ---------------------
    LeCameraObject* pcameraobj = new LeCameraObject();
    LeCameraManager::initMainCamera(pcameraobj);
    addGameObject(pcameraobj);

//    initializeLeste();
    initializeCAD();
//    initializeParticleDemo();
//    initializePostProcessingDemo();
//    initializePhysicsDemo();
}

void LeGameObjectManager::initializeLeste()
{
    // BackGround
    // ----------
    LeBackGroundGameObject* pbackgroundobj0 = new LeBackGroundGameObject();
    pbackgroundobj0->InitializeBackGroundGameObject(glm::vec3(13.f, 10.f, -50.0f),
                                                    glm::vec3(100.f, 100.f, 1.f),
                                                    "/Users/wangle/CLionProjects/Leste/res/shader/simplepng.vs",
                                                    "/Users/wangle/CLionProjects/Leste/res/shader/simplepng.fs",
                                                    "/Users/wangle/CLionProjects/Leste/res/background_0.png");
    LeBackGroundGameObject* pbackgroundobj1 = new LeBackGroundGameObject();
    pbackgroundobj1->InitializeBackGroundGameObject(glm::vec3(13.f, 10.f, -10.0f),
                                                    glm::vec3(40.f, 40.f, 1.f),
                                                    "/Users/wangle/CLionProjects/Leste/res/shader/simplepng.vs",
                                                    "/Users/wangle/CLionProjects/Leste/res/shader/simplepng.fs",
                                                    "/Users/wangle/CLionProjects/Leste/res/background_1.png");
    LeBackGroundGameObject* pbackgroundobj2 = new LeBackGroundGameObject();
    pbackgroundobj2->InitializeBackGroundGameObject(glm::vec3(13.f, 10.f, -5.0f),
                                                    glm::vec3(30.f, 30.f, 1.f),
                                                    "/Users/wangle/CLionProjects/Leste/res/shader/simplepng.vs",
                                                    "/Users/wangle/CLionProjects/Leste/res/shader/simplepng.fs",
                                                    "/Users/wangle/CLionProjects/Leste/res/background_2.png");
    addGameObject(pbackgroundobj0);
    addGameObject(pbackgroundobj1);
    addGameObject(pbackgroundobj2);

    // Ground
    // ------
    LeGroundGameObject* pgroundobj1 = new LeGroundGameObject();
    pgroundobj1->InitializeGroundGameObject(glm::vec3(0.f, 6.0f, 0.0f),
                                            glm::vec3(4.f, 12.f, 1.f),
                                            "/Users/wangle/CLionProjects/Leste/res/shader/simplepng.vs",
                                            "/Users/wangle/CLionProjects/Leste/res/shader/simplepng.fs",
                                            "/Users/wangle/CLionProjects/Leste/res/ground_4_12.png");
    LeGroundGameObject* pgroundobj2 = new LeGroundGameObject();
    pgroundobj2->InitializeGroundGameObject(glm::vec3(5.5f, 2.0f, 0.0f),
                                            glm::vec3(7.f, 4.f, 1.f),
                                            "/Users/wangle/CLionProjects/Leste/res/shader/simplepng.vs",
                                            "/Users/wangle/CLionProjects/Leste/res/shader/simplepng.fs",
                                            "/Users/wangle/CLionProjects/Leste/res/ground_7_4.png");
    LeGroundGameObject* pgroundobj3 = new LeGroundGameObject();
    pgroundobj3->InitializeGroundGameObject(glm::vec3(11.f, 2.0f, 0.0f),
                                            glm::vec3(2.f, 4.f, 1.f),
                                            "/Users/wangle/CLionProjects/Leste/res/shader/simplepng.vs",
                                            "/Users/wangle/CLionProjects/Leste/res/shader/simplepng.fs",
                                            "/Users/wangle/CLionProjects/Leste/res/ground_2_4.png");
    LeGroundGameObject* pgroundobj4 = new LeGroundGameObject();
    pgroundobj4->InitializeGroundGameObject(glm::vec3(13.f, 9.0f, 0.0f),
                                            glm::vec3(2.f, 4.f, 1.f),
                                            "/Users/wangle/CLionProjects/Leste/res/shader/simplepng.vs",
                                            "/Users/wangle/CLionProjects/Leste/res/shader/simplepng.fs",
                                            "/Users/wangle/CLionProjects/Leste/res/ground_2_4.png");
    LeGroundGameObject* pgroundobj5 = new LeGroundGameObject();
    pgroundobj5->InitializeGroundGameObject(glm::vec3(17.f, 4.0f, 0.0f),
                                            glm::vec3(4.f, 2.f, 1.f),
                                            "/Users/wangle/CLionProjects/Leste/res/shader/simplepng.vs",
                                            "/Users/wangle/CLionProjects/Leste/res/shader/simplepng.fs",
                                            "/Users/wangle/CLionProjects/Leste/res/ground_4_2.png");
    LeGroundGameObject* pgroundobj6 = new LeGroundGameObject();
    pgroundobj6->InitializeGroundGameObject(glm::vec3(20.f, 9.0f, 0.0f),
                                            glm::vec3(4.f, 2.f, 1.f),
                                            "/Users/wangle/CLionProjects/Leste/res/shader/simplepng.vs",
                                            "/Users/wangle/CLionProjects/Leste/res/shader/simplepng.fs",
                                            "/Users/wangle/CLionProjects/Leste/res/ground_4_2.png");
    LeGroundGameObject* pgroundobj7 = new LeGroundGameObject();
    pgroundobj7->InitializeGroundGameObject(glm::vec3(22.f, 5.0f, 0.0f),
                                            glm::vec3(2.f, 4.f, 1.f),
                                            "/Users/wangle/CLionProjects/Leste/res/shader/simplepng.vs",
                                            "/Users/wangle/CLionProjects/Leste/res/shader/simplepng.fs",
                                            "/Users/wangle/CLionProjects/Leste/res/ground_2_4.png");
    LeGroundGameObject* pgroundobj8 = new LeGroundGameObject();
    pgroundobj8->InitializeGroundGameObject(glm::vec3(26.f, 3.0f, 0.0f),
                                            glm::vec3(4.f, 2.f, 1.f),
                                            "/Users/wangle/CLionProjects/Leste/res/shader/simplepng.vs",
                                            "/Users/wangle/CLionProjects/Leste/res/shader/simplepng.fs",
                                            "/Users/wangle/CLionProjects/Leste/res/ground_4_2.png");
    addGameObject(pgroundobj1);
    addGameObject(pgroundobj2);
    addGameObject(pgroundobj3);
    addGameObject(pgroundobj4);
    addGameObject(pgroundobj5);
    addGameObject(pgroundobj6);
    addGameObject(pgroundobj7);
    addGameObject(pgroundobj8);

    // Player
    // ------
    LePlayerGameObject* pPlayer = new LePlayerGameObject();
    addGameObject(pPlayer);

    // Set Camera target
    LeCameraManager::setCameraTarget(pPlayer);
}

void LeGameObjectManager::initializeCAD()
{
    LeCubeGameObject* pCube0 = new LeCubeGameObject();
    pCube0->setParameters(glm::vec3(0,0.6,0), glm::vec3(0,0,0),glm::vec3(1,1,1));
    addGameObject(pCube0);
//    LeCubeGameObject* pCube0 = new LeCubeGameObject();
//    pCube0->setParameters(glm::vec3(3,0.6,-4), glm::vec3(0,0.0,0),glm::vec3(1,1,1));
//    addGameObject(pCube0);
//    LeCubeGameObject* pCube1 = new LeCubeGameObject();
//    pCube1->setParameters(glm::vec3(0,1.0f,1), glm::vec3(0.5,0.2,0.4),glm::vec3(1,1,1));
//    addGameObject(pCube1);
//    LeCubeGameObject* pCube2 = new LeCubeGameObject();
//    pCube2->setParameters(glm::vec3(2,0.6,0), glm::vec3(0,0.0,0),glm::vec3(1,1,1));
//    addGameObject(pCube2);
//    LeCubeGameObject* pCube3 = new LeCubeGameObject();
//    pCube3->setParameters(glm::vec3(0,0.6,-2), glm::vec3(0,0.3,0),glm::vec3(1,1,1));
//    addGameObject(pCube3);
//    LeCubeGameObject* pCube4 = new LeCubeGameObject();
//    pCube4->setParameters(glm::vec3(1,0.6,3), glm::vec3(0,0.0,0),glm::vec3(1,1,1));
//    addGameObject(pCube4);

//    // CubicBox
//    // --------
//    addGameObject(new LeCubicBoxGameObject());
//
//    float dist = 0.5f;
//    float scal = 0.5f;
//    LeCubicSubBoxGameObject* pXP = new LeCubicSubBoxGameObject();
//    pXP->setParameters(dist, 0, 0, scal, 0.1, 0.1, 1, 0, 0, 1);
//    LeCubicSubBoxGameObject* pXN = new LeCubicSubBoxGameObject();
//    pXN->setParameters(-dist, 0, 0, scal, 0.1, 0.1, 1, 0, 0, 1);
//    LeCubicSubBoxGameObject* pYP = new LeCubicSubBoxGameObject();
//    pYP->setParameters(0, dist, 0, 0.1, scal, 0.1, 0, 1, 0, 1);
//    LeCubicSubBoxGameObject* pYN = new LeCubicSubBoxGameObject();
//    pYN->setParameters(0, -dist, 0, 0.1, scal, 0.1, 0, 1, 0, 1);
//    LeCubicSubBoxGameObject* pZP = new LeCubicSubBoxGameObject();
//    pZP->setParameters(0, 0, dist, 0.1, 0.1, scal, 0, 0, 1, 1);
//    LeCubicSubBoxGameObject* pZN = new LeCubicSubBoxGameObject();
//    pZN->setParameters(0, 0, -dist, 0.1, 0.1, scal, 0, 0, 1, 1);
//    addGameObject(pXP);
//    addGameObject(pXN);
//    addGameObject(pYP);
//    addGameObject(pYN);
//    addGameObject(pZP);
//    addGameObject(pZN);

    addGameObject(new LeLightGameObject());
//    addGameObject(new LeQuadGameObject());
}

void LeGameObjectManager::initializeParticleDemo()
{
    LeCubeGameObject* pCube0 = new LeCubeGameObject();
    pCube0->setParameters(glm::vec3(0,-0.7,0), glm::vec3(0,0,0),glm::vec3(10,1,10));
    addGameObject(pCube0);

    addGameObject(new LeFireGameObject());
//    addGameObject(new LeLightGameObject());
    addGameObject(new LePostProcessingGameObject());
}


#define HDR_TEST1
#define BLOOM_TEST
#define DOF_TEST1
void LeGameObjectManager::initializePostProcessingDemo()
{
#ifdef HDR_TEST
    LeCubeGameObject* pCube0 = new LeCubeGameObject();
    pCube0->setParameters(glm::vec3(0,-2,10), glm::vec3(0,-0.0,0),glm::vec3(2,2,20));
    addGameObject(pCube0);
    LeCubeGameObject* pCube1 = new LeCubeGameObject();
    pCube1->setParameters(glm::vec3(0,0,0), glm::vec3(0,-0.0,0),glm::vec3(2,2,2));
    addGameObject(pCube1);

    addGameObject(new LeLightGameObject());
    addGameObject(new LePostProcessingGameObject());
#endif

#ifdef BLOOM_TEST
    LeCubeGameObject* pCube0 = new LeCubeGameObject();
    pCube0->setParameters(glm::vec3(3,0,-5), glm::vec3(0,0.0,0),glm::vec3(1,1,1));
    addGameObject(pCube0);
    LeCubeGameObject* pCube1 = new LeCubeGameObject();
    pCube1->setParameters(glm::vec3(-1,-0.5,1), glm::vec3(0.5,0.2,0.4),glm::vec3(1,1,1));
    addGameObject(pCube1);
    LeCubeGameObject* pCube2 = new LeCubeGameObject();
    pCube2->setParameters(glm::vec3(2,0,0), glm::vec3(0,0.0,0),glm::vec3(1,1,1));
    addGameObject(pCube2);

    addGameObject(new LeLightGameObject());
    addGameObject(new LePostProcessingGameObject());
#endif

#ifdef DOF_TEST
    LeCubeGameObject* pCube0 = new LeCubeGameObject();
    pCube0->setParameters(glm::vec3(3,0,-5), glm::vec3(0,0.0,0),glm::vec3(1,1,1));
    addGameObject(pCube0);
    LeCubeGameObject* pCube1 = new LeCubeGameObject();
    pCube1->setParameters(glm::vec3(0,-1,1), glm::vec3(0.5,0.2,0.4),glm::vec3(1,1,1));
    addGameObject(pCube1);
    LeCubeGameObject* pCube2 = new LeCubeGameObject();
    pCube2->setParameters(glm::vec3(2,0,0), glm::vec3(0,0.0,0),glm::vec3(1,1,1));
    addGameObject(pCube2);
    LeCubeGameObject* pCube3 = new LeCubeGameObject();
    pCube3->setParameters(glm::vec3(0,0,-3), glm::vec3(0,0.3,0),glm::vec3(1,1,1));
    addGameObject(pCube3);
    LeCubeGameObject* pCube4 = new LeCubeGameObject();
    pCube4->setParameters(glm::vec3(1,0,3), glm::vec3(0,0.0,0),glm::vec3(1,1,1));
    addGameObject(pCube4);

    addGameObject(new LeLightGameObject());

    addGameObject(new LePostProcessingGameObject());
#endif
}



void LeGameObjectManager::initializePhysicsDemo()
{
    LeQuadGameObject* pQuad0 = new LeQuadGameObject();
    pQuad0->setParameter(glm::vec3(0,0,0), glm::vec3(0,0,0),glm::vec3(10, 1, 1),
                        "/Users/wangle/CLionProjects/Leste/res/wood.png");
    addGameObject(pQuad0);
    LeQuadGameObject* pQuad1 = new LeQuadGameObject();
    pQuad1->setParameter(glm::vec3(-0.5,2,0), glm::vec3(0,0,-30.0f * 3.14 / 180.0f),glm::vec3(3, 0.1, 0.1),
                        "/Users/wangle/CLionProjects/Leste/res/wood.png");
    addGameObject(pQuad1);
    LeQuadGameObject* pQuad2 = new LeQuadGameObject();
    pQuad2->setParameter(glm::vec3(0.5,4,0), glm::vec3(0,0,60.0f * 3.14 / 180.0f),glm::vec3(3, 0.1, 0.1),
                         "/Users/wangle/CLionProjects/Leste/res/wood.png");
    addGameObject(pQuad2);

    LePhysicsQuadSpawnerGameObject* pSpawner = new LePhysicsQuadSpawnerGameObject();
    addGameObject(pSpawner);
}


void LeGameObjectManager::Awake() {
    for(auto iter = m_pMapGameObjects.begin(); iter != m_pMapGameObjects.end(); iter++)
    {
        LeGameObject* pgameobj = iter->second;
        if(pgameobj != nullptr)
            pgameobj->Awake();
    }
}

void LeGameObjectManager::PreRender() {
    for(auto iter = m_pMapGameObjects.begin(); iter != m_pMapGameObjects.end(); iter++)
    {
        LeGameObject* pgameobj = iter->second;
        if(pgameobj != nullptr)
            pgameobj->PreRender();
    }
}

void LeGameObjectManager::Start() {
    for(auto iter = m_pMapGameObjects.begin(); iter != m_pMapGameObjects.end(); iter++)
    {
        LeGameObject* pgameobj = iter->second;
        if(pgameobj != nullptr)
            pgameobj->Start();
    }
}


void LeGameObjectManager::RenderSelection()
{
    for(auto iter = m_pMapGameObjects.begin(); iter != m_pMapGameObjects.end(); iter++)
    {
        LeGameObject* pgameobj = iter->second;
        if(pgameobj != nullptr && pgameobj->m_bSelectable)
            pgameobj->RenderSelection();
    }
}

void LeGameObjectManager::updateCurMouseHoverID()
{
    int xx = LeInput::getMouseX() * 2; // for retina display
    int yy = LeInput::getMouseY() * 2; // for retina display

    unsigned char res[4];
    GLint viewport[4];

    glGetIntegerv(GL_VIEWPORT, viewport);
    glReadPixels(xx, viewport[3] - yy, 1,1,GL_RGBA, GL_UNSIGNED_BYTE, &res);

    m_CurMouseHoverID = res[0];
}

void LeGameObjectManager::Update() {
    for(auto iter = m_pMapGameObjects.begin(); iter != m_pMapGameObjects.end(); iter++)
    {
        LeGameObject* pgameobj = iter->second;
        if(pgameobj != nullptr)
            pgameobj->Update();
    }
}

void LeGameObjectManager::PostWorldStep()
{
    for(auto iter = m_pMapGameObjects.begin(); iter != m_pMapGameObjects.end(); iter++)
    {
        LeGameObject* pgameobj = iter->second;
        if(pgameobj != nullptr)
            pgameobj->PostWorldStep();
    }
}

void LeGameObjectManager::FixedUpdate() {
    for(auto iter = m_pMapGameObjects.begin(); iter != m_pMapGameObjects.end(); iter++)
    {
        LeGameObject* pgameobj = iter->second;
        if(pgameobj != nullptr)
            pgameobj->FixedUpdate();
    }
}

void LeGameObjectManager::Render() {
    for(auto iter = m_pMapGameObjects.begin(); iter != m_pMapGameObjects.end(); iter++)
    {
        LeGameObject* pgameobj = iter->second;
        if(pgameobj != nullptr)
            pgameobj->Render();
    }
    for(auto iter = m_pMapGameObjects.begin(); iter != m_pMapGameObjects.end(); iter++)
    {
        LeGameObject* pgameobj = iter->second;
        if(pgameobj != nullptr)
            pgameobj->PostprocessingRender();
    }
}

void LeGameObjectManager::PostRender() {
    for(auto iter = m_pMapGameObjects.begin(); iter != m_pMapGameObjects.end(); iter++)
    {
        LeGameObject* pgameobj = iter->second;
        if(pgameobj != nullptr)
            pgameobj->PostRender();
    }
}

void LeGameObjectManager::addGameObject(LeGameObject *pgameobj) { 
    m_pMapGameObjects[m_CurID] = pgameobj;
    pgameobj->ID = m_CurID;
    m_CurID++;

    pgameobj->Initialize();
    pgameobj->Awake();
    pgameobj->PreRender();
    pgameobj->Start(); // TODO: maybe Start() should move outside. Some GO may relate other initialized GO here.
}

LeGameObject *LeGameObjectManager::findGameObjectByID(unsigned int id) { 
    auto it = m_pMapGameObjects.find(id);
    if(it != m_pMapGameObjects.end())
        return m_pMapGameObjects.find(id)->second;
    else
        return nullptr;
}

void LeGameObjectManager::destroyGameObjectByID(unsigned int id) { 
    auto it = m_pMapGameObjects.find(id);
    if(it != m_pMapGameObjects.end())
    {
        LeGameObject *pgameobj = it->second;
        if(pgameobj != nullptr)
        {
            pgameobj->DestroyRigidBodyInWorld();
            delete pgameobj;
        }

        m_pMapGameObjects.erase(id);
    }
}

void LeGameObjectManager::addGameObjIDtoDestroy(unsigned int id) { 
    m_arrGameObjIDstoDestroy.push_back(id);
}

void LeGameObjectManager::destroyGameObjectsByIDs() { 
    for(auto id : m_arrGameObjIDstoDestroy)
    {
        destroyGameObjectByID(id);
    }
    m_arrGameObjIDstoDestroy.clear();
}

unsigned int LeGameObjectManager::getCurMouseHoverId() const
{
    return m_CurMouseHoverID;
}

void LeGameObjectManager::PostprocessingSetup()
{
    for(auto iter = m_pMapGameObjects.begin(); iter != m_pMapGameObjects.end(); iter++)
    {
        LeGameObject* pgameobj = iter->second;
        if(pgameobj != nullptr)
            pgameobj->PostprocessingSetup();
    }
}